using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
using AvansRS.Domain;
using System.Reflection;
using System.IO;
using AvansRS.Domain.DataInterface;
using StructureMap;

namespace AvansRS.Web.Controllers
{
	public class UploadController : Controller
	{
		private IAccountRepository repAccount;
		private IPersonRepository repPerson;
		private IGroupRepository repGroup;
		private IRepository<Student> repStudent;
		private IRepository<Entity> repEntity;

		private int linenumber = 0;
		private enum FileUploadResult { SUCCEEDED = 1, WARNING = 2, HEADER_ERROR = 3, FATAL_ERROR = 4 };

		public UploadController()
			: this(
				ObjectFactory.GetInstance<IAccountRepository>(), 
				ObjectFactory.GetInstance<IPersonRepository>(), 
				ObjectFactory.GetInstance<IGroupRepository>(),
				ObjectFactory.GetInstance<IRepository<Student>>(),
				ObjectFactory.GetInstance<IRepository<Entity>>()
			)
		{

		}

		public UploadController(
			IAccountRepository repAccount, 
			IPersonRepository repPerson, 
			IGroupRepository repGroup,
			IRepository<Student> repStudent,
			IRepository<Entity> repEntity
		)
		{
			this.repAccount = repAccount;
			this.repPerson = repPerson;
			this.repGroup = repGroup;
			this.repStudent = repStudent;
			this.repEntity = repEntity;
		}

		public ActionResult NewStudent()
		{
			if(TempData["ModelState"] as ModelStateDictionary != null)
			{
				ModelState.Merge(TempData["ModelState"] as ModelStateDictionary);
			}
			return View();
		}

		public ActionResult UploadNewGroup()
		{
			return HandleFile(true);
		}

		public ActionResult UploadNewStudent()
		{
			return HandleFile(false);
		}

		private ActionResult HandleFile(bool inGroup)
		{
			foreach(string inputTagName in Request.Files)
			{
				HttpPostedFileBase file = Request.Files[inputTagName];
				if(file.ContentLength > 0)
				{
					FileUploadResult uploadResult;
					if(inGroup)
					{
						uploadResult = ReadNewGroupFile(file);
					}
					else
					{
						uploadResult = ReadNewStudentFile(file);
					}
					switch(uploadResult)
					{
						case FileUploadResult.SUCCEEDED:
							{
								TempData["ModelState"] = ModelState;
								return RedirectToAction("Index", "User");
							}
						case FileUploadResult.WARNING:
							{
								TempData["ModelState"] = ModelState;
								return RedirectToAction("Index", "User");
							}
						case FileUploadResult.HEADER_ERROR:
							{
								TempData["ModelState"] = ModelState;
								return RedirectToAction("NewStudent", "Upload");
							}
						case FileUploadResult.FATAL_ERROR:
							{
								TempData["ModelState"] = ModelState;
								return RedirectToAction("NewStudent", "Upload");
							}
					}
				}
			}
			return RedirectToAction("NewStudent", "Upload");
		}

		#region File Readers
		#region New Group File Reader
		private FileUploadResult ReadNewGroupFile(HttpPostedFileBase file)
		{
			FileUploadResult result = FileUploadResult.SUCCEEDED;
			IList<Group> groups = new List<Group>();
			Group newGroup = null;
			GroupType? groupType = null;
			result = ValidateExtension(file);
			if(result != FileUploadResult.SUCCEEDED)
			{
				return result;
			}
			
			StreamReader streamReader = new StreamReader(file.InputStream);
			string[] headerNames = null;
			for(linenumber = 0; !streamReader.EndOfStream; linenumber++)
			{
				string line = streamReader.ReadLine();
				if(!string.IsNullOrEmpty(line) && !line.Trim().Equals(";"))
				{
					if(headerNames == null)
					{
						FileUploadResult headerResult = GetNewAccountHeaderNames(line, out headerNames);
						if(headerResult != FileUploadResult.SUCCEEDED)
						{
							result = headerResult;
							break;
						}
					}
					else if(groupType == null)
					{
						FileUploadResult groupTypeResult = GetGroupType(line, out groupType);
						if(groupTypeResult != FileUploadResult.SUCCEEDED)
						{
							result = groupTypeResult;
							break;
						}
					}
					else if(newGroup == null)
					{
						string[] splittedLine = line.Split(';');
						if(splittedLine.Length != 2 || !splittedLine[0].ToLower().Equals("group") || string.IsNullOrEmpty(splittedLine[1]))
						{
							ModelState.AddModelError("Group", string.Format("Regel {0} van het bestand bevat een syntactische fout."));
							result = FileUploadResult.FATAL_ERROR;
							break;
						}
						else
						{
							newGroup = new Group()
							{
								Name = splittedLine[1],
								GroupType = (GroupType)groupType,
								Year = DateTime.Now,
								Members = new List<Person>()
							};
						}
					}
					else
					{
						Account account;
						FileUploadResult accountResult = AddAccountToList(headerNames, line, out account);
						if(account != null)
						{
							Person person = new Person();
							person.Account = account;
							Student student = new Student();
							student.Person = person;
							person.StudentDetails = student;
							
							if(newGroup.Leader == null)
							{
								newGroup.Leader = person;
							}
							newGroup.Members.Add(person);
						}
						result = UpdateFileUploadResult(result, accountResult);
					}
				}
				else if(newGroup != null)
				{
					FileUploadResult groupResult = CreateGroupResult(newGroup);
					newGroup = null;
					result = UpdateFileUploadResult(result, groupResult);
				}
			}
			if(newGroup != null)
			{
				FileUploadResult groupResult = CreateGroupResult(newGroup);
				newGroup = null;
				result = UpdateFileUploadResult(result, groupResult);
			}
			return result;
		}

		private FileUploadResult CreateGroupResult(Group newGroup)
		{
			FileUploadResult groupResult = ValidateGroup(newGroup);
			if(groupResult == FileUploadResult.SUCCEEDED)
			{
				Person leader = repPerson.FindByAccountUsername(newGroup.Leader.Account.UserName);
				if(leader == null)
				{
					newGroup.Leader.ID = AddStudent(newGroup.Leader);
				}
				foreach(Person person in newGroup.Members)
				{
					Person foundPerson = repPerson.FindByAccountUsername(person.Account.UserName);
					if(foundPerson == null)
					{
						person.ID = AddStudent(person);
					}
					else
					{
						person.ID = foundPerson.ID;
					}
				}
				AddGroup(newGroup);
			}
			return groupResult;
		}
		#endregion
		#region New Student File Reader
		private FileUploadResult ReadNewStudentFile(HttpPostedFileBase file)
		{
			FileUploadResult result = FileUploadResult.SUCCEEDED;
			IList<Person> persons = new List<Person>();
			result = ValidateExtension(file);
			if(result != FileUploadResult.SUCCEEDED)
			{
				return result;
			}

			StreamReader streamReader = new StreamReader(file.InputStream);
			string[] headerNames = null;
			for(linenumber = 0; !streamReader.EndOfStream; linenumber++)
			{
				string line = streamReader.ReadLine();
				if(!string.IsNullOrEmpty(line))
				{
					if(headerNames == null)
					{
						FileUploadResult headerResult = GetNewAccountHeaderNames(line, out headerNames);
						if(headerResult != FileUploadResult.SUCCEEDED)
						{
							result = headerResult;
							break;
						}
					}
					else
					{
						Account account;
						FileUploadResult accountResult = AddAccountToList(headerNames, line, out account);
						if(account != null)
						{
							Person person = new Person();
							person.Account = account;
							Student student = new Student();
							student.Person = person;
							person.StudentDetails = student;

							persons.Add(person);
						}
						result = UpdateFileUploadResult(result, accountResult);
					}
				}
			}
			foreach(Person person in persons)
			{
				try
				{
					Account foundAccount = repAccount.FindByUsername(person.Account.UserName);
					if(foundAccount != null && foundAccount.UserName == person.Account.UserName)
					{
						throw new Exception("Gebruiker met username '" + foundAccount.UserName + "' bestaat al.");
					}
					person.ID = AddStudent(person);
				}
				catch (Exception ex)
				{
					result = FileUploadResult.WARNING;
					ModelState.AddModelError("Add", ex.Message);
				}
			}
			return result;
		}

		
		private FileUploadResult AddAccountToList(string[] headerNames, string line, out Account account)
		{
			account = new Account();
			account.ResetPassword();
			string[] splitted = line.Split(';');
			if(splitted.Length != headerNames.Length)
			{
				ModelState.AddModelError("Missing", "Regel " + linenumber + " van het bestand is genegeerd vanwege incomplete kolommen.");
				account = null;
				return FileUploadResult.WARNING;
			}
			for(int i = 0; i < headerNames.Length; i++)
			{
				if(String.IsNullOrEmpty(splitted[i]))
				{
					ModelState.AddModelError("Missing", "Regel " + linenumber + " van het bestand is genegeerd vanwege incomplete kolom '"+headerNames[i]+"' .");
					account = null;
					return FileUploadResult.WARNING;
				}
				switch(headerNames[i].ToLower())
				{
					case "gebruikersnaam":
						{
							account.UserName = splitted[i];
							break;
						}
					case "email":
						{
							account.Email = splitted[i];
							break;
						}
				}
			}
			return FileUploadResult.SUCCEEDED;
		}
		#endregion
		private FileUploadResult GetGroupType(string line, out GroupType? groupType)
		{
			string[] splittedLine = line.Split(';');
			groupType = null;
			if(splittedLine.Length != 2 || !splittedLine[0].ToLower().Equals("grouptype"))
			{
				ModelState.AddModelError("GroupType", string.Format("Regel {0} van het bestand bevat geen informatie over 'GroupType'.", linenumber));
				return FileUploadResult.FATAL_ERROR;
			}
			switch(splittedLine[1])
			{
				case "Group":
					{
						groupType = GroupType.Group;
						break;
					}
				case "ProjectGroup":
					{
						groupType = GroupType.ProjectGroup;
						break;
					}
				case "ExcursionGroup":
					{
						groupType = GroupType.ExcursionGroup;
						break;
					}
				case "LessonGroup":
					{
						groupType = GroupType.LessonGroup;
						break;
					}
				default:
					{
						ModelState.AddModelError("GroupType", string.Format("Regel {0} van het bestand bevat incorrecte informatie over 'GroupType'.", linenumber));
						return FileUploadResult.FATAL_ERROR;
					}
			}
			return FileUploadResult.SUCCEEDED;
		}

		private FileUploadResult GetNewAccountHeaderNames(string line, out string[] headerNames)
		{
			headerNames = line.Split(';');
			Dictionary<string, bool> requirednames = new Dictionary<string, bool>();
			requirednames.Add("gebruikersnaam", false);
			requirednames.Add("email", false);
			for(int i = 0; i < headerNames.Length; i++)
			{
				bool used = false;
				if(!requirednames.TryGetValue(headerNames[i], out used))
				{
					ModelState.AddModelError("Header", "Het bestand bevat verkeerde header (" + headerNames[i] + ").");
					return FileUploadResult.HEADER_ERROR;
				}
				else if(!used)
				{
					requirednames[headerNames[i]] = true;
				}
			}
			foreach(string key in requirednames.Keys)
			{
				if(!requirednames[key])
				{
					ModelState.AddModelError("Header", "Het bestand bevat geen informatie over '" + key + "'.");
					return FileUploadResult.HEADER_ERROR;
				}
			}
			return FileUploadResult.SUCCEEDED;
		}

		private FileUploadResult UpdateFileUploadResult(FileUploadResult currentResult, FileUploadResult newResult)
		{
			if(currentResult < newResult)
			{
				return newResult;
			}
			return currentResult;
		}

		private FileUploadResult ValidateExtension(HttpPostedFileBase file)
		{
			if(!file.FileName.EndsWith("csv"))
			{
				ModelState.AddModelError("Extension", "You only can upload a file with the extension 'csv'");
				return FileUploadResult.FATAL_ERROR;
			}
			return FileUploadResult.SUCCEEDED;
		}

		private FileUploadResult ValidateGroup(Group group)
		{
			Group foundGroup = repGroup.GetByName(group.Name);
			if(foundGroup != null)
			{
				ModelState.AddModelError("Unique", String.Format("Groupsnaam '{0}' bestaat al. De hele groep is genegeerd.", group.Name));
				return FileUploadResult.WARNING;
			}
			return FileUploadResult.SUCCEEDED;
		}

		private int AddStudent(Person newStudent)
		{
			newStudent.Entity = new Entity();
			repEntity.Add(newStudent.Entity);

			repAccount.Add(newStudent.Account);
			Student studentDetails = newStudent.StudentDetails;
			newStudent.StudentDetails = null;
			repPerson.AddWithoutValidate(newStudent);

			newStudent.StudentDetails = studentDetails;
			repStudent.Add(newStudent.StudentDetails);

			repPerson.SaveWithoutValidate(newStudent);
			return newStudent.ID;
		}

		private int AddGroup(Group newGroup)
		{
			newGroup.Entity = new Entity();
			IRepository<Entity> repEntity = ObjectFactory.GetInstance<IRepository<Entity>>();
			repEntity.Add(newGroup.Entity);
			repGroup.Add(newGroup);
			return newGroup.ID;
		}
		#endregion
	}
}
